//
//  Error.swift
//  ParsecMock
//
//  Created by Keith on 2018/6/27.
//  Copyright © 2018 Keith. All rights reserved.
//

import PaversFRP

/**
-- | This abstract data type represents parse error messages. There are
  -- four kinds of messages:
--
-- >  data Message = SysUnExpect String
-- >               | UnExpect String
-- >               | Expect String
-- >               | Message String
--
-- The fine distinction between different kinds of parse errors allows
-- the system to generate quite good error messages for the user. It
  -- also allows error messages that are formatted in different
-- languages. Each kind of message is generated by different combinators:
--
--     * A 'SysUnExpect' message is automatically generated by the
--       'Text.Parsec.Combinator.satisfy' combinator. The argument is the
--       unexpected input.
--
--     * A 'UnExpect' message is generated by the 'Text.Parsec.Prim.unexpected'
--       combinator. The argument describes the
--       unexpected item.
--
--     * A 'Expect' message is generated by the 'Text.Parsec.Prim.<?>'
--       combinator. The argument describes the expected item.
--
--     * A 'Message' message is generated by the 'fail'
--       combinator. The argument is some general parser message.
*/

public enum Message {
  /// library generated unexpect
  case sysUnExpect(String)
  /// unexpected something
  case unexpect(String)
  /// expecting something
  case expect(String)
  /// raw message
  case message(String)
}


extension Message {
  public var fromEnum: Int {
    switch self {
    case .sysUnExpect(_): return 0
    case .unexpect(_): return 1
    case .expect(_): return 2
    case .message(_): return 3
    }
  }
}

extension Message: Equatable {
  public static func == (lhs: Message, rhs: Message) -> Bool {
    return lhs.fromEnum == rhs.fromEnum
  }
}

extension Message: Comparable{
  public static func < (lhs: Message, rhs: Message) -> Bool {
    return lhs.fromEnum < rhs.fromEnum
  }
}

extension Message {
  public var message: String {
    switch self {
    case .sysUnExpect(let s): return s
    case .unexpect(let s): return s
    case .expect(let s): return s
    case .message(let s): return s
    }
  }
}

/**
 -- | The abstract data type @ParseError@ represents parse errors. It
 -- provides the source position ('SourcePos') of the error
 -- and a list of error messages ('Message'). A @ParseError@
 -- can be returned by the function 'Text.Parsec.Prim.parse'. @ParseError@ is an
 -- instance of the 'Show' and 'Eq' classes.
 */
public struct ParserError {
  public let pos: SourcePos
  public let msgs: [Message]
}

extension ParserError: Equatable{}

extension ParserError {
  public var errorIsUnknown: Bool {return self.msgs.isEmpty}
  
  public init(unknownErrorWith pos: SourcePos) {
    self.pos = pos
    self.msgs = []
  }
  
  public init(newErrorWith msg: Message, pos: SourcePos) {
    self.pos = pos
    self.msgs = [msg]
  }
  
  
  public func add(error: Message) -> ParserError {
    return ParserError(pos: self.pos, msgs: self.msgs + [error])
  }
  
  public func set(sourcePos: SourcePos) -> ParserError {
    return ParserError(pos: sourcePos, msgs: self.msgs)
  }
  
  public func set(errorMessages: [Message]) -> ParserError {
    return ParserError(pos: self.pos, msgs: errorMessages)
  }
  
//  mergeError :: ParseError -> ParseError -> ParseError
//  mergeError e1@(ParseError pos1 msgs1) e2@(ParseError pos2 msgs2)
//  -- prefer meaningful errors
//  | null msgs2 && not (null msgs1) = e1
//  | null msgs1 && not (null msgs2) = e2
//  | otherwise
//  = case pos1 `compare` pos2 of
//  -- select the longest match
//  EQ -> ParseError pos1 (msgs1 ++ msgs2)
//  GT -> e1
//  LT -> e2
  public func merge(error: ParserError) -> ParserError {
    if error.msgs.isEmpty && !self.msgs.isEmpty {
      return self
    } else if self.msgs.isEmpty && !error.msgs.isEmpty {
      return error
    } else {
      if self.pos == error.pos {
        return ParserError(pos: self.pos, msgs: self.msgs + error.msgs)
      } else {
        return self.pos > error.pos ? self : error
      }
    }
  }
}

extension ParserError: Semigroup {
  public func op(_ other: ParserError) -> ParserError {
    return self.merge(error: other)
  }
}


extension ParserError: CustomStringConvertible {
  public var description: String {
    return "\(self.pos): \(showErrorMessage(msgs:self.msgs))"
  }
}

public func showErrorMessage(msgOr: String = "msgOr",
                             msgUnknown: String = "msgUnknown",
                             msgExpecting: String = "msgExpecting",
                             msgUnexpected: String = "msgUnexpected",
                             msgEndOfInput: String = "msgEndOfInput",
                             msgs: [Message]) -> String {
  guard !msgs.isEmpty else {return msgUnknown}
  
  func clean(_ ss: [String]) -> [String] {
    return Array(Set(ss.filter{!$0.isEmpty}))
  }
  
  func seperate(_ seperator: String, _ ss: [String]) -> String {
    guard !ss.isEmpty else {return ""}
    guard ss.count > 1 else { return ss[0]}
    return ss.dropFirst().reduce(ss[0]) { (acc, s) in acc + seperator + s }
  }
  
  let commaSep = curry(seperate)(",")
  
  func commasOr(_ ms: [String]) -> String {
    guard !ms.isEmpty else {return ""}
    guard  ms.count > 1 else { return ms[0] }
    return commaSep( Array(ms.dropLast()) ) + " " + msgOr + " " + ms.last!
  }
  
  /// String -> [Message] -> String
  func showMany(pre: String, msgs: [Message]) -> String {
    let ss = clean(msgs.map{$0.message})
    guard !ss.isEmpty else {return ""}
    if pre.isEmpty {return commasOr(ss)}
    else {return pre + " " + commasOr(ss)}
  }
  
  let (sysUnExpect, msgs1) = msgs.span(curry((==))(Message.sysUnExpect("")))
  let (unExpect, msgs2) = msgs1.span(curry((==))(Message.unexpect("")))
  let (expect, messages) = msgs2.span(curry((==))(Message.expect("")))
  let showMessages = showMany(pre: "", msgs: messages)
  let firstMsg  = sysUnExpect.first?.message
  let showExpect = showMany(pre: msgExpecting, msgs: expect)
  let showUnExpect = showMany(pre: msgUnexpected, msgs: unExpect)
  let showSysUnExpect = (!unExpect.isEmpty || sysUnExpect.isEmpty)
    ? ""
    : ((firstMsg?.isEmpty ?? true) ? msgUnexpected + " " + msgEndOfInput : msgUnexpected + " " + (firstMsg ?? ""))
  return clean([showSysUnExpect,showUnExpect,showExpect,showMessages])
    .map{"\n" + $0}
    .reduce("", +)
}


//mergeErrorReply :: ParseError -> Reply s u a -> Reply s u a
//mergeErrorReply err1 reply -- XXX where to put it?
//= case reply of
//Ok x state err2 -> Ok x state (mergeError err1 err2)
//Error err2      -> Error (mergeError err1 err2)

public func mergeErrorReply<S, U, A>(err1: ParserError, reply: Reply<S, U, A>) -> Reply<S, U, A> {
  switch reply {
  case let .ok(x, state, err2): return .ok(x, state, err1.merge(error: err2))
  case let .error(err2): return .error(err1.merge(error: err2))
  }
}


//addErrorMessage :: Message -> ParseError -> ParseError
//addErrorMessage msg (ParseError pos msgs)
//= ParseError pos (msg:msgs)

public func addErrorMessage(msg: Message, e: ParserError) -> ParserError {
  return ParserError(pos: e.pos, msgs: e.msgs + [msg])
}

public func addErrorMessage_(e: ParserError, msg: Message) -> ParserError {
  return ParserError(pos: e.pos, msgs: e.msgs + [msg])
}

//unexpectError :: String -> SourcePos -> ParseError
//unexpectError msg pos = newErrorMessage (SysUnExpect msg) pos
public func unexpectError(s: String, pos: SourcePos) -> ParserError {
  return ParserError.init(newErrorWith: Message.sysUnExpect(s), pos: pos)
}

public func unknownError<S, U>(_ s: ParserState<S, U>) -> ParserError {
  return ParserError(unknownErrorWith: s.statePos)
}

public func sysUnExpectError<S, U, A>(_ msg: String, _ pos: SourcePos) -> Reply<S, U, A> {
  return Reply<S, U, A>
    .error(ParserError(newErrorWith: Message.sysUnExpect(msg),
                       pos: pos))
}
